孙鑫VC++第15课:多线程与聊天室02

您所在的位置:网站首页 vc6 多线程 孙鑫VC++第15课:多线程与聊天室02

孙鑫VC++第15课:多线程与聊天室02

2024-07-13 15:06| 来源: 网络整理| 查看: 265

3、使用多线程实现聊天室功能

新建MFC工程,通过向导创建一个基于对话框的应用程序。

3.1 界面设计

删除对话框原来默认的组件,添加组框(caption="接收数据"),在组框中放置编辑框(ID=IDC_EDIT_RECV);下面再增加一个组框(caption="发送数据")其中添加一个ip地址控件,右侧增加一个编辑框(ID=IDC_EDIT_SEND),添加一个按钮(caption="发送",ID=ID_BTN_SEND)。

界面如下所示:

 

3.2 加载套接字库

网络编程第一步,加载套接字库,进行套接字库版本协商。在MFC中,提供了一个加载套接字库和完成版本协商的函数AfxSocketInit。

BOOL AfxSocketInit(WSADATA * lpwsaData = NULL);//lpwsaData:指向WSADATA结构体的指针

函数内部加载套接字库、进行版本协商(加载的是1.1版本的套接字库)。函数可确保应用程序终止之前,调用wsacleanup终止对套接字的使用。

Remarks:在CWinApp::InitInstance中调用该函数。

调用AfxSocketInit函数时,需要包含一个头文件Afxsock.h

BOOL CChatApp::InitInstance() { if(!AfxSocketInit())//套接字库加载失败则返回 { AfxMessageBox(_T("加载套接字库失败!")); return FALSE; }

 

3.3 创建套接字

在CChatDlg类中,增加一个成员函数,完成套接字的初始化。BOOL CChatDlg::InitSocket()

CChatDlg类中增加成员变量private SOCKET m_socket(套接字描述符)

在套接字初始化函数中创建套接字。

1 BOOL CChatDlg::InitSocket(void) 2 { 3 m_socket = socket(AF_INET, SOCK_DGRAM, 0);//创建一个基于UDP的套接字 4 if (INVALID_SOCKET == m_socket) 5 { 6 AfxMessageBox(_T("套接字创建失败!")); 7 return FALSE; 8 } 9 /*本程序既包含接收端,又包含发送端。接收端程序需要绑定IP地址和端口号*/ 10 SOCKADDR_IN addSock; 11 addSock.sin_family = AF_INET; 12 addSock.sin_port = htons(6000); 13 addSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY); 14 15 //绑定 16 int ret; 17 ret = bind(m_socket, (SOCKADDR*)&addSock, sizeof(SOCKADDR)); 18 if (SOCKET_ERROR == ret)//绑定失败则返回 19 { 20 closesocket(m_socket);//关闭套接字 21 AfxMessageBox(_T("绑定失败")); 22 return FALSE; 23 } 24 return TRUE; 25 }

在OnInitDialog函数中调用Socket的初始化函数

1 BOOL CChatDlg::OnInitDialog() 2 { 3 CDialog::OnInitDialog(); 4 5 // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 6 // 执行此操作 7 SetIcon(m_hIcon, TRUE); // 设置大图标 8 SetIcon(m_hIcon, FALSE); // 设置小图标 9 10 // TODO: 在此添加额外的初始化代码 11 InitSocket();//初始化套接字 12 return TRUE; // 除非将焦点设置到控件,否则返回 TRUE 13 }

 

3.4 接收端程序设计

当接收数据时,在没有数据到来时,函数recvfrom会阻塞,导致程序暂停运行。

因此应该把接收数据的工作放到一个单独的线程中完成。

3.4.1 接收端线程创建

使用CreateThread函数创建线程,并给线程传递两个参数:一是创建的套接字;二是对话框的句柄或者“数据接收编辑框”的句柄。如此在线程中接收到数据后,可以将数据传回给对话框或编辑框,用于显示在,界面中。

由于CreateThread函数只接受一个指针变量作为参数,因此需要创建一个结构体,将待传递的两个参数包到结构体中进行传递。在CChatDlg的头文件中定义结构体。

1 //接收数据参数的结构体 2 struct RECVPARAM 3 { 4 SOCKET sock; 5 HWND hWnd; 6 };

再在CChatDialog::OnInitDlg中定义该结构体指针,并初始化数据。

BOOL CChatDlg::OnInitDialog() { CDialog::OnInitDialog(); // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 // 执行此操作 SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 // TODO: 在此添加额外的初始化代码 InitSocket();//初始化套接字 RECVPARAM* pRecvParam = new RECVPARAM; pRecvParam->sock = m_socket; pRecvParam->hWnd = m_hWnd;//对话框类与窗口相关的句柄

使用CreateThread创建线程

1 BOOL CChatDlg::OnInitDialog() 2 { 3 CDialog::OnInitDialog(); 4 5 // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 6 // 执行此操作 7 SetIcon(m_hIcon, TRUE); // 设置大图标 8 SetIcon(m_hIcon, FALSE); // 设置小图标 9 10 // TODO: 在此添加额外的初始化代码 11 InitSocket();//初始化套接字 12 RECVPARAM* pRecvParam = new RECVPARAM; 13 pRecvParam->sock = m_socket; 14 pRecvParam->hWnd = m_hWnd;//对话框类与窗口相关的句柄 15 16 HANDLE hThreadRecv = CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParam, 0, NULL);//创建接收线程 17 CloseHandle(hThreadRecv);//关闭线程句柄 18 return TRUE; // 除非将焦点设置到控件,否则返回 TRUE 19 }

3.4.2 线程函数设计

不能将线程函数定义为CChatDlg类的普通成员函数,因为当创建一个线程时,系统(运行时代码)需要调用线程函数启动线程。如果线程函数是CChatDlg类的成员函数,在调用成员函数前,需要先构造CChatDlg类的对象,通过对象调用成员函数。运行时代码无法确定如何生成对象。

解决方法是将线程函数定义成类的静态函数(或者全局函数)。

public: static DWORD WINAPI RecvProc(LPVOID lpParameter); 1 DWORD WINAPI CChatDlg::RecvProc(LPVOID lpParameter) 2 { 3 //取出所传递的参数值 4 SOCKET sock = ((RECVPARAM*)lpParameter)->sock; 5 HWND hWnd = ((RECVPARAM*)lpParameter)->hWnd; 6 7 SOCKADDR_IN addrFrom;//用于接收发送端的地址信息 8 int len = sizeof(SOCKADDR); 9 10 char recvBuf[200];//用于保存接收到的数据 11 char tempBuf[300];//存放格式化后的数据 12 13 int retval; 14 while(TRUE) 15 { 16 retval = recvfrom(sock, recvBuf, 200, 0, (SOCKADDR*)&addrFrom, &len);//接收数据 17 if (SOCKET_ERROR == retval)//接收错误则返回 18 { 19 break; 20 } 21 sprintf(tempBuf, "%s说:%s", inet_ntoa(addrFrom.sin_addr), recvBuf);//格式化数据 22 //将接收到的数据传递给对话框(使用发送消息的方式,并在CChatDlg头文件中定义消息的值) 23 ::PostMessage(hWnd, WM_RECVDATA, 0, (LPARAM)tempBuf); 24 } 25 26 return 0; 27 }

3.4.3 自定义消息及响应

3.4.3.1 自定义消息

在CChatDlg头文件中定义消息

#define WM_RECVDATA WM_USER+1

3.4.3.2 定义消息响应函数

在CChatDlg头文件中定义消息响应函数

afx_msg void OnRecvData(WPARAM wParam, LPARAM lParam);//声明消息响应函数 DECLARE_MESSAGE_MAP()

3.4.3.3 定义消息映射(消息与消息响应函数的映射)

BEGIN_MESSAGE_MAP(CChatDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_MESSAGE(WM_RECVDATA, OnRecvData);//完成消息映射 //}}AFX_MSG_MAP END_MESSAGE_MAP()

3.4.3.4 消息响应函数的设计

1 void CChatDlg::OnRecvData(WPARAM wParam, LPARAM lParam) 2 { 3 CString str = (char*)lParam;//保存新接收的数据 4 CString strOld;//保存聊天记录 5 GetDlgItemText(IDC_EDIT_RECV, strOld);//获取旧的聊天记录 6 str+="\r\n";//增加换行 7 str+=strOld;//拼接旧的聊天记录 8 SetDlgItemText(IDC_EDIT_RECV, str);//显示接收到的数据 9 }

 

3.5 发送端程序设计

在"发送"按钮的Click控件点击响应中,完成发送功能设计。

从IP地址控件中,获取对应的ip地址。与ip地址控件对应的类CIPAddressCtrl,通过其成员函数GetAddress获取控件中的IP地址。

int CIPAddressCtrl::GetAddress(DWORD &dwAddress);

 

1 void CChatDlg::OnBnClickedButtonSend() 2 { 3 // TODO: Add your control notification handler code here 4 DWORD dwIP;//接收用户输入的IP地址 5 ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS1))->GetAddress(dwIP);//获取用户输入的IP 6 7 SOCKADDR_IN addrTo; 8 addrTo.sin_family = AF_INET; 9 addrTo.sin_port = htons(6000); 10 addrTo.sin_addr.S_un.S_addr = htonl(dwIP); 11 12 CString strSend; 13 //char strSend[200]; 14 //memset(strSend, 0, 200); 15 //USES_CONVERSION; 16 GetDlgItemText(IDC_EDIT_SEND, strSend); 17 sendto(m_socket, (LPCSTR)(LPCTSTR)strSend, strSend.GetLength()+1, 0, (SOCKADDR*)&addrTo, sizeof(SOCKADDR)); 18 SetDlgItemText(IDC_EDIT_SEND, _T(""));//设置发送框内容为空 19 }

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3